
#include <string>
#include <iostream>
#include <memory>
#include <cstdint>
#include <cstring>

#include <tenacitas_command/sender.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>

#ifndef INADDR_NONE
#define INADDR_NONE 0xFFFFFFFF
#endif

namespace ten_cmd = tenacitas::command;


namespace tenacitas {
    namespace echo_client {

        struct message_types {
            typedef std::string message;
            typedef std::shared_ptr<message> message_ptr;
        };

        struct app_data_types {
            typedef message_types::message app_data;
            typedef message_types::message_ptr app_data_ptr;
        };

        struct connection_types {
            
            struct connection {
                typedef std::shared_ptr<connection> ptr;

                connection( int p_socket )
                    : m_socket( p_socket ) {}

                connection( ):
                    m_socket( -1 ) {}

                bool operator==( int p_i ) {
                    return ( p_i == m_socket );
                }

                connection & operator =( int p_i ) {
                    m_socket = p_i;
                    return *this;
                }

                operator int( ) const {
                    return m_socket;
                }

                int socket( ) const {
                    return m_socket;
                }
                        
            private:
                int m_socket;
            };

            typedef std::shared_ptr<connection> connection_ptr;
        };

        struct connector_types {

            typedef connection_types::connection connection;
            typedef connection_types::connection_ptr connection_ptr;

            struct connector {

                connector( const std::string & p_host,
                           const std::string & p_service )
                    : m_host( p_host ),
                      m_service( p_service ) {}
                

                connection_ptr connect( ) {

                    struct hostent * phe = nullptr;
                    struct servent * pse = nullptr;
                    struct protoent * ppe = nullptr;
                    struct sockaddr_in sin;
                    int s, type = SOCK_STREAM;

                    memset( &sin, 0, sizeof( sin ) );
                    sin.sin_family = AF_INET;

                    if ( ( pse = ::getservbyname( m_service.c_str( ), "tcp" ) ) ) {
                        sin.sin_port = pse->s_port;
                    }
                    else if ( ( sin.sin_port =
                                ::htons( ( unsigned short )
                                         ::atoi( m_service.c_str( ) ) ) ) == 0 )
                        { 
                            return connection_ptr( );
                        }

                    if ( ( phe = ::gethostbyname( m_host.c_str( ) ) ) ) {
                        memcpy( &sin.sin_addr, phe->h_addr, phe->h_length );
                    }
                    else if ( ( sin.sin_addr.s_addr =
                                ::inet_addr( m_host.c_str( ) ) ) = INADDR_NONE )
                        { 
                            return connection_ptr( );
                        }
                    
                    if ( ( ppe = ::getprotobyname( "tcp" ) )  == 0 ) {
                        return connection_ptr( );
                    }


                    s = ::socket( PF_INET, type, ppe->p_proto );
                    if ( s < 0 ) {

                        return connection_ptr( );
                    }

                    if ( ::connect( s,
                                    ( struct sockaddr * )&sin,
                                    sizeof( sin ) ) < 0 ) {
                        return connection_ptr( );
                    }

                    return connection_ptr( new connection( s ) );                    

                }
                
                    
            private:
                std::string m_host;
                std::string m_service;
            };

            typedef std::shared_ptr<connector> connector_ptr;

            struct connector_factory {

                connector_factory( const std::string & p_host,
                                   const std::string & p_service )
                    : m_host( p_host ),
                      m_service( p_service ) {
                }

                connector_ptr create( ) {
                    return connector_ptr( new connector( m_host, m_service ) );
                }

            private:
                std::string m_host;
                std::string m_service;
            };

            typedef std::shared_ptr<connector_factory> connector_factory_ptr;
        };

        
        struct translator_types {
            typedef message_types::message message;
            typedef message_types::message_ptr message_ptr;
            
            typedef app_data_types::app_data app_data;
            typedef app_data_types::app_data_ptr app_data_ptr;

            struct translator {
                app_data_ptr to_app_data( const message_ptr p_message ) {
                    return p_message;
                }

                message_ptr to_message( const app_data_ptr p_app_data ) {
                    return p_app_data;
                }
            };

            typedef std::shared_ptr<translator> translator_ptr;

            struct translator_factory {

                translator_ptr create( ) {
                    return translator_ptr( new translator( ) );
                }
            };

            typedef std::shared_ptr<translator_factory> translator_factory_ptr;
        };

        struct message_io_types {

            typedef connection_types::connection_ptr connection_ptr;
            typedef message_types::message message;                    
            typedef message_types::message_ptr message_ptr;

            struct message_io {
                
                message_ptr read( connection_ptr p_con ) {
                    static const int16_t l_buf_size = 2048;
                    
                    char l_buf[l_buf_size];
                    std::memset( l_buf, '\0', l_buf_size );
                    
                    if ( ::read( p_con->socket( ), l_buf, l_buf_size ) <= 0 ) {
                        return message_ptr( );
                    }
                        
                    return message_ptr( new message( l_buf ) );
                }

                bool write( connection_ptr p_con,
                            const message_ptr p_message ) {
                        
                    if ( ::write( p_con->socket( ),
                                  p_message->c_str( ),
                                  p_message->size( ) ) < 0 ) {
                        return false;
                    }
                    return true;
                }
            };

            typedef std::shared_ptr<message_io> message_io_ptr;
            
            struct message_io_factory {
                message_io_ptr create( ) {
                    return message_io_ptr( new message_io( ) );
                }            
            };

            typedef std::shared_ptr<message_io_factory> message_io_factory_ptr;
        };

        struct timeout_types {
            typedef std::int16_t timeout;
            typedef std::shared_ptr<timeout> timeout_ptr;

            struct timeout_factory {
                timeout_factory( )
                    : m_timeout( nullptr ){}
                
                timeout_ptr create( ) {
                    if ( !m_timeout ) {
                        m_timeout =  std::make_shared<timeout>( );
                    }
                    return m_timeout;
                }
            private:
                timeout_ptr m_timeout; 
            };

            typedef std::shared_ptr<timeout_factory> timeout_factory_ptr;

        };

        /** dummy */
        typedef int16_t log_types;

        typedef translator_types::translator_factory translator_factory;
        typedef translator_types::translator_factory_ptr translator_factory_ptr;

        typedef connector_types::connector_factory connector_factory;
        typedef connector_types::connector_factory_ptr connector_factory_ptr;

        typedef connector_types::connector_factory connector_factory;
        typedef connector_types::connector_factory_ptr connector_factory_ptr;

        typedef timeout_types::timeout_factory timeout_factory;
        typedef timeout_types::timeout_factory_ptr timeout_factory_ptr;

        typedef message_io_types::message_io_factory message_io_factory;
        typedef message_io_types::message_io_factory_ptr message_io_factory_ptr;

        typedef app_data_types::app_data app_data;
        typedef app_data_types::app_data_ptr app_data_ptr;                
        
        typedef ten_cmd::sender<message_types,
                                app_data_types,
                                connector_types,
                                connection_types,
                                message_io_types,
                                translator_types,
                                timeout_types,
                                log_types> echo_sender;
    }
}

int main( ) {
    
    using namespace tenacitas::echo_client;

    try {
        
        translator_factory_ptr l_translator_factory =
            std::make_shared<translator_factory>( ); 
    
        connector_factory_ptr l_connector_factory =
            std::make_shared<connector_factory>( "127.0.0.1", "45001" ); 

        timeout_factory_ptr l_timeout_factory = std::make_shared<timeout_factory>( ); 

        message_io_factory_ptr l_message_io_factory =
            std::make_shared<message_io_factory>( ); 
    

        echo_sender l_sender( l_translator_factory,
                              l_connector_factory,
                              l_timeout_factory,
                              l_message_io_factory );
    

        app_data_ptr l_to_send( new app_data( "ola, como vai voce?" ) );
    
        app_data_ptr l_reply = l_sender.send( l_to_send );

        if ( l_reply ) {
            std::cout << "echo: '" << *l_reply << "'" << std::endl;
        }
        else {
            std::cout << "No reply " << std::endl;
        }
    }
    catch( std::string & l_ex ) {
        std::cout << "echo: '" << l_ex << "'" << std::endl;
    }
    
    
}
